/**
 * @author Jason Pratt
 */

package edu.cmu.cs.stage3.alice.authoringtool;

import edu.cmu.cs.stage3.alice.authoringtool.event.AuthoringToolStateChangedEvent;
import edu.cmu.cs.stage3.alice.authoringtool.event.AuthoringToolStateListener;
import edu.cmu.cs.stage3.alice.authoringtool.event.UndoRedoListener;
import edu.cmu.cs.stage3.alice.core.Element;
import edu.cmu.cs.stage3.alice.core.Property;
import edu.cmu.cs.stage3.alice.core.event.ChildrenListener;

public class MainUndoRedoStack extends edu.cmu.cs.stage3.alice.authoringtool.util.DefaultUndoRedoStack
       implements edu.cmu.cs.stage3.alice.core.event.PropertyListener, edu.cmu.cs.stage3.alice.core.event.ObjectArrayPropertyListener,
                  edu.cmu.cs.stage3.alice.core.event.ChildrenListener, AuthoringToolStateListener {

 protected AuthoringTool authoringTool;
 private boolean isListening = true;
 private boolean inCompound = false;
 private edu.cmu.cs.stage3.alice.authoringtool.util.CompoundUndoableRedoable compoundItem;
 private int unmodifiedIndex = -1;
 protected java.util.HashSet listeners = new java.util.HashSet();

 // Came from: edu.cmu.cs.stage3.alice.authoringtool.AuthoringTool.mainInit(AuthoringTool.java:303)
 public MainUndoRedoStack (AuthoringTool authoringTool) {
  this.authoringTool = authoringTool;
  // System.out.println("MainUndoRedoStack.java: MainUndoRedoStack(AuthoringTool): authoringTool="+authoringTool);
  // try{String str=""; System.out.println(str.charAt(3));}catch(Exception e){e.printStackTrace();}
  }

 public void addUndoRedoListener (UndoRedoListener listener) {
  listeners.add( listener );
  }

 public int getUnmodifiedIndex () {
  return unmodifiedIndex;
  }

 synchronized protected void fireChange () {
  for (java.util.Iterator iter = listeners.iterator(); iter.hasNext();) {
   ((UndoRedoListener)iter.next()).onChange();
   }
  }

 synchronized public void setUnmodified () {
  unmodifiedIndex = getCurrentUndoableRedoableIndex();
  fireChange();
  }

 synchronized public void setIsListening (boolean isListening) {
  this.isListening = isListening;
  }

 synchronized public boolean getIsListening () {
  return isListening;
  }

 synchronized public void startCompound () {
  if (!inCompound) {
   compoundItem = new edu.cmu.cs.stage3.alice.authoringtool.util.CompoundUndoableRedoable();
   compoundItem.setContext( authoringTool.getContext() );
   // System.out.println("MainUndoRedoStack.java: startCompound(): "+authoringTool+" = "+AuthoringTool.getInstance()+"?");
   // authoringTool.getContext="+authoringTool.getContext()); - it is allways returning 'null' !!!
   push( compoundItem );
   inCompound = true;
   }
  }

 synchronized public void stopCompound () {
  inCompound = false;
  }

 //Rem @Override
 synchronized public edu.cmu.cs.stage3.alice.authoringtool.util.UndoableRedoable undo () {
  edu.cmu.cs.stage3.alice.authoringtool.util.UndoableRedoable ur = null;
  try {
   stopCompound();
   boolean temp = isListening;
   isListening = false;
   // edu.cmu.cs.stage3.alice.authoringtool.util.UndoableRedoable ur = super.undo();
   ur = super.undo(); // edu.cmu.cs.stage3.alice.authoringtool.util.DefaultUndoRedoStack.undo() -> edu.cmu.cs.stage3.alice.authoringtool.util.CompoundUndoableRedoable.undo()
   loadContext( ur.getContext() ); //L Why is that? Seems no effect, 'ur' is 'AuthoringTool.getContext()' that allways return 'null'!
   isListening = temp;
   fireChange();
   // System.out.println("MainUndoRedoStack.java: undo(): "+edu.cmu.cs.stage3.util.StrUtilities.getComponentName(ur)+": "+ur.getContext());
   return ur;
  } catch (Exception e) { e.printStackTrace(); return ur; }
  }

 //Rem @Override
 synchronized public edu.cmu.cs.stage3.alice.authoringtool.util.UndoableRedoable redo () {
  stopCompound();
  boolean temp = isListening;
  isListening = false;
  int currentIndex = getCurrentUndoableRedoableIndex();
  if (currentIndex < (size() - 1)) {
   loadContext( ((edu.cmu.cs.stage3.alice.authoringtool.util.UndoableRedoable)get( currentIndex + 1 )).getContext() );
   }
  edu.cmu.cs.stage3.alice.authoringtool.util.UndoableRedoable ur = super.redo();
  isListening = temp;
  fireChange();
  return ur;
  }

 synchronized protected void loadContext (Object context) {
  authoringTool.setContext( context );
  }

 //Rem @Override
 synchronized public void push (edu.cmu.cs.stage3.alice.authoringtool.util.UndoableRedoable ur) {
  //  Thread.dumpStack();
  if (ur instanceof edu.cmu.cs.stage3.alice.authoringtool.util.ContextAssignableUndoableRedoable) {
   ((edu.cmu.cs.stage3.alice.authoringtool.util.ContextAssignableUndoableRedoable)ur).setContext( authoringTool.getContext() );
   }
  if (inCompound) {
   compoundItem.addItem( ur );
  } else {
   super.push( ur );
   }
  fireChange();
  }

 //Rem @Override
 synchronized public edu.cmu.cs.stage3.alice.authoringtool.util.UndoableRedoable pop () {
  stopCompound();
  edu.cmu.cs.stage3.alice.authoringtool.util.UndoableRedoable ur = super.pop();
  fireChange();
  return ur;
  }

 //Rem @Override
 synchronized public void clear () {
  super.clear();
  setUnmodified();
  fireChange();
  }

 //Rem @Override
 synchronized public edu.cmu.cs.stage3.alice.authoringtool.util.UndoableRedoable removeUndoable (int index) {
  edu.cmu.cs.stage3.alice.authoringtool.util.UndoableRedoable ur = super.removeUndoable( index );
  fireChange();
  return ur;
  }

 protected Object preChangeValue;

 synchronized public void propertyChanging (edu.cmu.cs.stage3.alice.core.event.PropertyEvent propertyEvent) {
  if (isListening) {
   //TODO: I need to be getting a clone here...?
   preChangeValue = propertyEvent.getProperty().get();
   }
  }

 synchronized public void propertyChanged (edu.cmu.cs.stage3.alice.core.event.PropertyEvent propertyEvent) {
  if (isListening) {
   // ObjectArrayProperties are handled separately
   if (propertyEvent.getProperty() instanceof edu.cmu.cs.stage3.alice.core.property.ObjectArrayProperty) {
    return;
    }

   // if the property change is for a property being changed by a currently running one-shot, we ignore the change
   if (authoringTool.getOneShotScheduler().isPropertyAffected( propertyEvent.getProperty() )) {
    return;
    }

   push( new edu.cmu.cs.stage3.alice.authoringtool.util.PropertyUndoableRedoable( propertyEvent.getProperty(), preChangeValue, propertyEvent.getProperty().get() ) );
   }
  }

 synchronized public void objectArrayPropertyChanging (edu.cmu.cs.stage3.alice.core.event.ObjectArrayPropertyEvent ev) {}

 synchronized public void objectArrayPropertyChanged (edu.cmu.cs.stage3.alice.core.event.ObjectArrayPropertyEvent ev) {
  if (isListening) {
   // if the property change is for a property being changed by a currently running one-shot, we ignore the change
   if (authoringTool.getOneShotScheduler().isPropertyAffected( ev.getObjectArrayProperty() )) {
    return;
    }
   push( new edu.cmu.cs.stage3.alice.authoringtool.util.ObjectArrayPropertyUndoableRedoable( ev.getObjectArrayProperty(), ev.getChangeType(), ev.getOldIndex(), ev.getNewIndex(), ev.getItem() ) );
   }
  }

 synchronized public void childrenChanging (edu.cmu.cs.stage3.alice.core.event.ChildrenEvent childrenEvent) {}

 synchronized public void childrenChanged (edu.cmu.cs.stage3.alice.core.event.ChildrenEvent childrenEvent) {
  if (isListening) {
   push( new edu.cmu.cs.stage3.alice.authoringtool.util.ChildChangeUndoableRedoable( childrenEvent ) );
   }
  int changeType = childrenEvent.getChangeType();
  if (changeType == edu.cmu.cs.stage3.alice.core.event.ChildrenEvent.CHILD_INSERTED) {
   listenTo( childrenEvent.getChild() );
  } else if (changeType == edu.cmu.cs.stage3.alice.core.event.ChildrenEvent.CHILD_REMOVED) {
   stopListeningTo( childrenEvent.getChild() );
   }
  }

 synchronized public void listenTo (edu.cmu.cs.stage3.alice.core.Element element) {
  if (element != null) {
   edu.cmu.cs.stage3.alice.core.Element[] elements = element.getDescendants();
   //LOB for (Element element2 : elements)
   Element element2 = null; //L
   int t_tam = elements!=null ? elements.length : 0;
   for (int i_i=0; i_i<t_tam; i_i++) {
    element2 = elements[i_i]; //L
    edu.cmu.cs.stage3.alice.core.Property [] properties = element2.getProperties();
    //LOB for (Property propertie : properties)
    Property propertie = null;
    int tt_tam = properties!=null ? properties.length : 0;
    for (int i_j=0; i_j<tt_tam; i_j++) {
     propertie = properties[i_j]; //L
     //T String str = edu.cmu.cs.stage3.util.StrUtilities.getComponentName(element)+"): "+edu.cmu.cs.stage3.util.StrUtilities.getComponentName(propertie);
     //T System.out.println("MainUndoRedoStack.java: listenTo("+str); }
     propertie.addPropertyListener( this );
     if (propertie instanceof edu.cmu.cs.stage3.alice.core.property.ObjectArrayProperty) {
      ((edu.cmu.cs.stage3.alice.core.property.ObjectArrayProperty)propertie).addObjectArrayPropertyListener( this ); // edu.cmu.cs.stage3.alice.core.property.ObjectArrayProperty.addObjectArrayPropertyListener(this)
      }
     } // for (int i_j=0; i_j<tt_tam; i_j++)
    boolean alreadyChildrenListening = false;
    edu.cmu.cs.stage3.alice.core.event.ChildrenListener[] childrenListeners = element2.getChildrenListeners();
    //LOB for (ChildrenListener childrenListener : childrenListeners)
    tt_tam = childrenListeners!=null ? childrenListeners.length : 0;
    for (int i_j=0; i_j<tt_tam; i_j++) {
     if (childrenListeners[i_j] == this) { //L childrenListener == this
      alreadyChildrenListening = true;
      }
     }
    if (! alreadyChildrenListening) {
     element2.addChildrenListener( this );
     }
    } // for (int i_i=0; i_i<t_tam; i_i++)
   }
  }

 synchronized public void stopListeningTo (edu.cmu.cs.stage3.alice.core.Element element) {
  if (element != null) {
   edu.cmu.cs.stage3.alice.core.Element[] elements = element.getDescendants();
   //LOB for (Element element2 : elements)
   Element element2 = null; //L
   int t_tam = elements!=null ? elements.length : 0;
   for (int i_i=0; i_i<t_tam; i_i++) {
    element2 = elements[i_i]; //L
    edu.cmu.cs.stage3.alice.core.Property [] properties = element2.getProperties();
    //LOB for (Property propertie : properties)
    Property propertie = null;
    int tt_tam = properties!=null ? properties.length : 0;
    for (int i_j=0; i_j<tt_tam; i_j++) {
     propertie = properties[i_j]; //L
     propertie.removePropertyListener( this );
     if (propertie instanceof edu.cmu.cs.stage3.alice.core.property.ObjectArrayProperty) {
      ((edu.cmu.cs.stage3.alice.core.property.ObjectArrayProperty)propertie).removeObjectArrayPropertyListener( this );
      }
     } // for (int i_j=0; i_j<tt_tam; i_j++)
    element2.removeChildrenListener( this );
    } // for (int i_i=0; i_i<t_tam; i_i++)
   }
  }

 ///////////////////////////////////////////////
 // AuthoringToolStateListener interface
 ///////////////////////////////////////////////
 public void stateChanged (AuthoringToolStateChangedEvent ev) {
  if (ev.getCurrentState() == AuthoringToolStateChangedEvent.RUNTIME_STATE) {
   stopListeningTo( ev.getWorld() );
  } else {
   listenTo( ev.getWorld() );
   }
  }

 public void worldUnLoading (AuthoringToolStateChangedEvent ev) {
  stopListeningTo( ev.getWorld() );
  }

 public void worldLoaded (AuthoringToolStateChangedEvent ev) {
  listenTo( ev.getWorld() );
  }

 public void stateChanging( AuthoringToolStateChangedEvent ev) {}
 public void worldLoading( AuthoringToolStateChangedEvent ev) {}
 public void worldStarting( AuthoringToolStateChangedEvent ev) {}
 public void worldStopping( AuthoringToolStateChangedEvent ev) {}
 public void worldPausing( AuthoringToolStateChangedEvent ev) {}
 public void worldSaving( AuthoringToolStateChangedEvent ev) {}
 public void worldUnLoaded( AuthoringToolStateChangedEvent ev) {}
 public void worldStarted( AuthoringToolStateChangedEvent ev) {}
 public void worldStopped( AuthoringToolStateChangedEvent ev) {}
 public void worldPaused( AuthoringToolStateChangedEvent ev) {}
 public void worldSaved( AuthoringToolStateChangedEvent ev) {}

 }
